/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2007 OPeNDAP, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
/////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.IO;
using System.Text;
using NDODS.DAP.Exceptions;
using NDODS.DAP.Utils;
using NDODS.DAP.XMLParser;
using NDODS.Util;

namespace NDODS.DAP
{
    /// <summary> An <code>AttributeTable</code> stores a set of names and, for each name,
    /// an <code>Attribute</code> object.  For more information on the types of
    /// data which can be stored in an attribute, including aliases and other
    /// <code>AttributeTable</code> objects, see the documentation for
    /// <code>Attribute</code>.
    /// <p/>
    /// The attribute tables have a standard printed representation.  There is a
    /// <code>Print</code> method for writing this form and a <code>parse</code>
    /// method for reading the printed form.
    /// <p/>
    /// An <code>AttributeTable</code>'s Print representation might look like:
    /// <pre>
    /// String long_name "Weekly Means of Sea Surface Temperature";
    /// </pre>
    /// or
    /// <pre>
    /// actual_range {
    /// Float64 min -1.8;
    /// Float64 max 35.09;
    /// }
    /// </pre>
    /// or
    /// <pre>
    /// String Investigators "Cornillon", "Fleirl", "Watts";
    /// </pre>
    /// or
    /// <pre>
    /// Alias New_Attribute Old_Attribute;
    /// </pre>
    /// Here, <em>long_name</em> and <em>Investigators</em> are
    /// simple attributes, <em>actual_range</em> is a container attribute, and
    /// <em>New_Attribute</em> is an alias pointing to <em>Old_Attribute</em>.
    /// 
    /// </summary>
    /// <author>  jehamby
    /// </author>
    /// <version>  $Revision: 15901 $
    /// </version>
    /// <seealso cref="DAS">
    /// </seealso>
    /// <seealso cref="Attribute">
    /// </seealso>
    [Serializable]
    public class AttributeTable : ICloneable
    {
        //UPGRADE_NOTE: Respective javadoc comments were merged.  It should be changed in order to comply with .NET documentation conventions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1199'"
        /// <summary> Returns the name of this AttributeTable.</summary>
        /// <summary> Returns the name of this AttributeTable.</summary>
        public virtual String Name
        {
            get { return EscapeStrings.id2www(_name); }

            set { _name = EscapeStrings.www2id(value); }
        }

        //UPGRADE_NOTE: Respective javadoc comments were merged.  It should be changed in order to comply with .NET documentation conventions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1199'"
        /// <summary> Returns the name of this AttributeTable.</summary>
        /// <summary> Returns the name of this AttributeTable.</summary>
        public virtual String ClearName
        {
            get { return _name; }

            set { _name = value; }
        }

        /// <summary> Returns an <code>Enumeration</code> of the attribute names in this
        /// <code>AttributeTable</code>.
        /// Use the <code>GetAttribute</code> method to get the
        /// <code>Attribute</code> for a given name.
        /// 
        /// </summary>
        /// <returns> an <code>Enumeration</code> of <code>String</code>.
        /// </returns>
        /// <seealso cref="AttributeTable.getAttribute(String)">
        /// </seealso>
        public virtual ArrayList Names
        {
            get { return _attr.keys(); }
        }

        internal const long serialVersionUID = 1;

        //private static final bool _Debug = false;

        /// <summary> A table of Attributes with their names as a key</summary>
        private SortedTable _attr;

        /// <summary> What's the name of this table?</summary>
        private String _name;

        /// <summary> Create a new empty <code>AttributeTable</code>.
        /// 
        /// </summary>
        /// <deprecated> Use constructor that takes the name of the table.
        /// </deprecated>
        public AttributeTable()
        {
            _attr = new SortedTable();
        }

        /// <summary> Create a new empty <code>AttributeTable</code>.</summary>
        public AttributeTable(String name)
        {
            Name = name;
            _attr = new SortedTable();
        }

        /// <summary> Returns a clone of this <code>AttributeTable</code>.  A deep copy is
        /// performed on all <code>Attribute</code> and <code>AttributeTable</code>
        /// objects inside the <code>AttributeTable</code>.
        /// 
        /// </summary>
        /// <returns> a clone of this <code>AttributeTable</code>.
        /// </returns>
        public virtual Object Clone()
        {
            try
            {
                AttributeTable at = (AttributeTable) MemberwiseClone();
                at._name = _name;
                at._attr = new SortedTable();
                for (int i = 0; i < _attr.size(); i++)
                {
                    String key = (String) _attr.getKey(i);
                    Attribute element = (Attribute) _attr.elementAt(i);
                    // clone element (don't clone key because it's a Read-only String)
                    at._attr.put(key, element.Clone());
                }
                return at;
            }
                //UPGRADE_NOTE: Exception 'java.lang.CloneNotSupportedException' was converted to 'System.Exception' which has different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1100'"
            catch (Exception)
            {
                // this shouldn't happen, since we are Cloneable
                throw new ApplicationException();
            }
        }

        /// <summary> Returns the <code>Attribute</code> which matches name.
        /// 
        /// </summary>
        /// <param name="name">the name of the <code>Attribute</code> to return.
        /// </param>
        /// <returns> the <code>Attribute</code> with the specified name, or null
        /// if there is no matching <code>Attribute</code>.
        /// </returns>
        /// <seealso cref="Attribute">
        /// </seealso>
        public Attribute getAttribute(String name)
        {
            //throws NoSuchAttributeException {
            Attribute a = (Attribute) _attr[name];

            //        if(a == null){
            //            throw new NoSuchAttributeException("There is no Attribute named '" + name + "' "+
            //	                                       "in the AttributeTable '" + getName() + "'");
            //        }

            return (a);
        }

        /// <summary> Returns the <code>Attribute</code> which matches name.
        /// 
        /// </summary>
        /// <param name="name">the name of the <code>Attribute</code> to return.
        /// </param>
        /// <returns> True if an Attribute with named 'name' exists, False otherwise.
        /// </returns>
        /// <seealso cref="Attribute">
        /// </seealso>
        public bool hasAttribute(String name)
        {
            Attribute a = (Attribute) _attr[name];

            if (a == null)
            {
                return (false);
            }
            return (true);
        }

        /// <summary> Adds an attribute to the table.  If the given name already
        /// refers to an attribute, and the attribute has a vector value,
        /// the given value is appended to the attribute vector.  Calling
        /// this function repeatedly is the way to create an attribute
        /// vector.
        /// <p/>
        /// The function throws an exception if the attribute is a
        /// container, or if the type of the input value does not match the
        /// existing attribute's type and the <code>check</code> parameter
        /// is true.  Use the <code>appendContainer</code> method to add container
        /// attributes.
        /// 
        /// </summary>
        /// <param name="name"> The name of the attribute to add or modify.
        /// </param>
        /// <param name="type"> The type code of the attribute to add or modify.
        /// </param>
        /// <param name="value_Renamed">The value to add to the attribute table.
        /// </param>
        /// <param name="check">Check the validity of the attribute's value?
        /// </param>
        /// <throws>  AttributeExistsException   thrown if an Attribute with the same </throws>
        /// <summary>                                    name, but a different type was previously defined.
        /// </summary>
        /// <throws>  AttributeBadValueException thrown if the value is not a legal </throws>
        /// <summary>                                    member of type
        /// </summary>
        /// <seealso cref="AttributeTable.appendContainer(String)">
        /// </seealso>
        public void appendAttribute(String name, int type, String value_Renamed, bool check)
        {
            Attribute a = (Attribute) _attr[name];

            if (a != null && (type != a.Type))
            {
                // type mismatch error
                throw new AttributeExistsException("The Attribute `" + name +
                                                   "' was previously defined with a different type.");
            }
            else if (a != null)
            {
                a.appendValue(value_Renamed, check);
            }
            else
            {
                a = new Attribute(type, name, value_Renamed, check);
                _attr.put(name, a);
            }
        }

        /// <summary> Adds an attribute to the table.  If the given name already
        /// refers to an attribute, and the attribute has a vector value,
        /// the given value is appended to the attribute vector.  Calling
        /// this function repeatedly is the way to create an attribute
        /// vector.
        /// <p/>
        /// The function throws an exception if the attribute is a
        /// container, or if the type of the input value does not match the
        /// existing attribute's type.  Use the <code>appendContainer</code>
        /// method to add container attributes.
        /// 
        /// </summary>
        /// <param name="name"> The name of the attribute to add or modify.
        /// </param>
        /// <param name="type"> The type code of the attribute to add or modify.
        /// </param>
        /// <param name="value_Renamed">The value to add to the attribute table.
        /// </param>
        /// <throws>  AttributeExistsException   thrown if an Attribute with the same </throws>
        /// <summary>                                    name, but a different type was previously defined.
        /// </summary>
        /// <throws>  AttributeBadValueException thrown if the value is not a legal </throws>
        /// <summary>                                    member of type
        /// </summary>
        /// <seealso cref="AttributeTable.appendContainer(String)">
        /// </seealso>
        public void appendAttribute(String name, int type, String value_Renamed)
        {
            appendAttribute(name, type, value_Renamed, true);
        }

        /// <summary> Create and append an attribute container to the table.
        /// A container is another <code>AttributeTable</code> object.
        /// 
        /// </summary>
        /// <param name="name">the name of the container to add.
        /// </param>
        /// <returns> A pointer to the new <code>AttributeTable</code> object, or null
        /// if a container by that name already exists.
        /// </returns>
        public AttributeTable appendContainer(String name)
        {
            // return null if name already exists
            // FIXME! THIS SHOULD RETURN AN EXCEPTION!
            if (_attr[name] != null)
                return null;

            AttributeTable at = new AttributeTable(name);
            Attribute a = new Attribute(name, at);
            _attr.put(name, a);
            return at;
        }

        /// <summary> Create and append an attribute container to the table.
        /// A container is another <code>AttributeTable</code> object.
        /// 
        /// </summary>
        /// <param name="name">the name of the container to add.
        /// if a container by that name already exists.
        /// </param>
        /// <param name="at"></param>
        public void addContainer(String name, AttributeTable at)
        {
            // return null if name already exists
            if (_attr[name] != null)
            {
                throw new AttributeExistsException("The Attribute '" + name + "' already exists in the container '" +
                                                   Name + "'");
            }

            Attribute a = new Attribute(name, at);
            _attr.put(name, a);
        }

        /// <summary> Add an alias to the current table.
        /// This method is used by the DAS parser to build Aliases
        /// for the DAS. And the DDSXMLParser to add them to the DDX
        /// <p/>
        /// The new (9/26/02) DDS requires the use of <code>
        /// addAlias(String, String, String)</code> and is the preffered
        /// way of representing the DAS information.
        /// 
        /// </summary>
        /// <param name="alias">        The alias to insert into the attribute table.
        /// </param>
        /// <param name="attributeName">The normalized name of the attribute to which
        /// the alias will refer.
        /// </param>
        /// <throws>  NoSuchAttributeException thrown if the existing attribute </throws>
        /// <summary>                                  could not be found.
        /// </summary>
        /// <throws>  AttributeExistsException thrown if the new alias has the same </throws>
        /// <summary>                                  name as an existing attribute.
        /// </summary>
        public void addAlias(String alias, String attributeName)
        {
            // complain if alias name already exists in this AttributeTable.
            if (_attr[alias] != null)
            {
                throw new AttributeExistsException("Could not alias `" + alias + "' to `" + attributeName + "'. " +
                                                   "It is a duplicat name in this AttributeTable");
            }
            if (Debug.isSet("AttributTable"))
            {
                Console.Out.WriteLine("Adding alias '" + alias + "' to AttributeTable '" + _name + "'");
            }

            Alias newAlias = new Alias(alias, attributeName);
            _attr.put(alias, newAlias);
        }

        /// <summary> Delete the attribute named <code>name</code>.
        /// 
        /// </summary>
        /// <param name="name">The name of the attribute to delete.  This can be an
        /// attribute of any type, including containers.
        /// </param>
        public void delAttribute(String name)
        {
            _attr.remove(name);
        }

        /// <summary> Delete the attribute named <code>name</code>.  If the attribute has a
        /// vector value, delete the <code>i</code>'th element of the vector.
        /// 
        /// </summary>
        /// <param name="name">The name of the attribute to delete.  This can be an
        /// attribute of any type, including containers.
        /// </param>
        /// <param name="i">   If the named attribute is a vector, and <code>i</code> is
        /// non-negative, the <code>i</code>'th entry in the vector is deleted.
        /// If <code>i</code> equals -1, the entire attribute is deleted.
        /// </param>
        /// <seealso cref="AttributeTable.delAttribute(String)">
        /// </seealso>
        public void delAttribute(String name, int i)
        {
            if (i == - 1)
            {
                // delete the whole attribute

                _attr.remove(name);
            }
            else
            {
                Attribute a = (Attribute) _attr[name];

                if (a != null)
                {
                    if (a.isContainer())
                    {
                        _attr.remove(name); // delete the entire container
                    }
                    else
                    {
                        a.deleteValueAt(i);
                    }
                }
            }
        }

        /// <summary> Print the attribute table on the given <code>StreamWriter</code>.
        /// 
        /// </summary>
        /// <param name="os"> the <code>StreamWriter</code> to use for output.
        /// </param>
        /// <param name="pad">the number of spaces to indent each line.
        /// </param>
        public virtual void print(StreamWriter os, String pad)
        {
            if (Debug.isSet("AttributTable"))
            {
                //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
                os.WriteLine("Entered AttributeTable.Print()");
            }

            //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
            os.WriteLine(pad + Name + " {");
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            for (IEnumerator e = Names.GetEnumerator(); e.MoveNext();)
            {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                String name = (String) e.Current;
                Attribute a = getAttribute(name);
                if (a != null)
                    a.print(os, pad + "    ");
            }
            //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
            os.WriteLine(pad + "}");
            if (Debug.isSet("AttributTable"))
            {
                //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
                os.WriteLine("Leaving AttributeTable.Print()");
            }
            os.Flush();
        }

        /// <summary> Print the attribute table on the given <code>OutputStream</code>.
        /// 
        /// </summary>
        /// <param name="os"> the <code>OutputStream</code> to use for output.
        /// </param>
        /// <param name="pad">the number of spaces to indent each line.
        /// </param>
        public void print(Stream os, String pad)
        {
            //UPGRADE_WARNING: At least one expression was used more than once in the target code. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1181'"
            print(
                new StreamWriter(os, Encoding.Default), pad);
        }

        /// <summary> Print the attribute table on the given <code>StreamWriter</code> with
        /// four spaces of indentation.
        /// 
        /// </summary>
        /// <param name="os">the <code>StreamWriter</code> to use for output.
        /// </param>
        public void print(StreamWriter os)
        {
            print(os, "");
        }

        /// <summary> Print the attribute table on the given <code>OutputStream</code> with
        /// four spaces of indentation.
        /// 
        /// </summary>
        /// <param name="os">the <code>OutputStream</code> to use for output.
        /// </param>
        public void print(Stream os)
        {
            print(os, "");
        }


        /// <summary> </summary>
        /// <param name="os">
        /// </param>
        /// <opendap.ddx.experimental>  </opendap.ddx.experimental>
        public virtual void printXML(Stream os)
        {
            printXML(os, "");
        }


        /// <summary> </summary>
        /// <param name="os">
        /// </param>
        /// <param name="pad">
        /// </param>
        /// <opendap.ddx.experimental>  </opendap.ddx.experimental>
        public virtual void printXML(Stream os, String pad)
        {
            //UPGRADE_WARNING: At least one expression was used more than once in the target code. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1181'"
            StreamWriter pw =
                new StreamWriter(
                    new StreamWriter(new StreamWriter(os, Encoding.Default).BaseStream,
                                     new StreamWriter(os, Encoding.Default).Encoding).BaseStream,
                    new StreamWriter(new StreamWriter(os, Encoding.Default).BaseStream,
                                     new StreamWriter(os, Encoding.Default).Encoding).Encoding);
            printXML(pw, pad);
            pw.Flush();
        }


        /// <summary> </summary>
        /// <param name="pw">
        /// </param>
        /// <opendap.ddx.experimental>  </opendap.ddx.experimental>
        public virtual void printXML(StreamWriter pw)
        {
            printXML(pw, "");
        }


        /// <summary> </summary>
        /// <param name="pw">
        /// </param>
        /// <param name="pad">
        /// </param>
        /// <opendap.ddx.experimental>  </opendap.ddx.experimental>
        public virtual void printXML(StreamWriter pw, String pad)
        {
            printXML(pw, pad, false);
        }


        /// <summary> </summary>
        /// <param name="pw">
        /// </param>
        /// <param name="pad">
        /// </param>
        /// <param name="constrained">
        /// </param>
        /// <opendap.ddx.experimental>  </opendap.ddx.experimental>
        public virtual void printXML(StreamWriter pw, String pad, bool constrained)
        {
            if (Debug.isSet("AttributTable"))
            {
                //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
                pw.WriteLine("Entered AttributeTable.Print()");
            }

            //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
            pw.WriteLine(pad + "<Attribute name=\"" + DDSXMLParser.normalizeToXML(Name) + "\" type=\"Container\">");

            IEnumerator e = Names.GetEnumerator();
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            while (e.MoveNext())
            {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                String name = (String) e.Current;
                Attribute a = getAttribute(name);
                if (a != null)
                    a.printXML(pw, pad + "\t", constrained);
            }
            //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
            pw.WriteLine(pad + "</Attribute>");
            if (Debug.isSet("AttributTable"))
            {
                //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
                pw.WriteLine("Leaving AttributeTable.Print()");
            }
            pw.Flush();
        }
    }

    // $Log: AttributeTable.java,v $
    // Revision 1.3  2003/09/02 17:49:34  ndp
    // *** empty log message ***
    //
    // Revision 1.2  2003/09/02 15:06:25  ndp
    // *** empty log message ***
    //
    // Revision 1.1  2003/08/12 23:51:25  ndp
    // Mass check in to begin Java-OPeNDAP development work
    //
    // Revision 1.11  2003/04/07 22:12:32  jchamber
    // added serialization
    //
    // Revision 1.10  2003/02/12 16:41:15  ndp
    // *** empty log message ***
    //
    // Revision 1.9  2002/10/10 18:12:31  ndp
    // Fixed bugs in DDS.getDAS(), Updated testDataset and sqlDataset
    //
    // Revision 1.8  2002/10/08 21:59:18  ndp
    // Added XML functionality to the core. This includes the new DDS code (aka DDX)
    // for parsing XML representations of the dataset description ( that's a DDX)
    // Also BaseType has been modified to hold Attributes and methods added to DDS
    // to ingest DAS's (inorder to add Attributes to variables) and to get the DAS
    // object from the DDS. Geturl and DConnect hav been modified to provide client
    // access to this new set of functionalites. ndp 10/8/2002
    //
    // Revision 1.7  2002/05/30 23:25:57  jimg
    // I added methods that provide a way to add attribues without the usual
    // type/value checking. See today's log in Attribute.java for more info.
    //
}